package sc.server;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import sc.datainterchange.CodedOutputBuffer;
import sc.server.world.Avatar;
import sc.server.world.Container;

public final class Client
{
	// Map of known Clients
	private static Map<String, Client> clients = new HashMap<String, Client>();
	
	/**
	 * Returns a known Client instance.
	 * 
	 * @param name
	 *            Unique id of client
	 * 
	 * @return a Client instance
	 * @throws SQLException 
	 */
	public static Client getClient(String name) throws SQLException
	{
		// Do we already have a Player instance for this player?
		if (clients.containsKey(name))
			return clients.get(name);
		
		// No, so create one
		Client client = new Client(name);
		clients.put(name, client);
		return client;
	}
	
	
	// Pending coded data to be sent
	public static final int DEFAULT_BUFFER_SIZE = 4096;
	private final ByteBuffer outputbuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
	private final CodedOutputBuffer codedoutput = new CodedOutputBuffer(outputbuffer);

	// Clients attributes
	private String name;
	private String password;
	private Avatar avatar;
	
	/**
	 * Creates an instance of Client.
	 * @throws SQLException 
	 */
	public Client(String name) throws SQLException
	{
		this.name = name;
		this.password = null;
		this.avatar = Avatar.get(name);
	}
	
	/**
	 * Sets the name.
	 * 
	 * @param name
	 * 			New name
	 */
	public void setName(String name)
	{
		this.name = name;
	}
	
	/**
	 * Returns the name.
	 * 
	 * @return name
	 */
	public String getName()
	{
		return this.name;
	}
	
	/**
	 * Returns the clients Avatar.
	 * 
	 * @return An instance of Avatar
	 */
	public Avatar getAvatar()
	{
		return this.avatar;
	}
	
	/**
	 * Sets the password.
	 * 
	 * @param password
	 * 			New password
	 */
	public void setPassword(String password)
	{
		this.password = password;
	}
	
	/**
	 * Returns the password.
	 * 
	 * @return password
	 */
	public String getPassword()
	{
		return this.password;
	}
	
	/**
	 * Asks the client to send an authentication message.
	 * 
	 * @throws IOException 
	 */
	public void writeAuthenticate() throws IOException
	{
		this.codedoutput.writeRawVarint32(Protocol.AUTHENTICATE);
	}
	
	/**
	 * Tells the client its authenticated.
	 * 
	 * @throws IOException 
	 */
	public void writeAuthenticated() throws IOException
	{
		this.codedoutput.writeRawVarint32(Protocol.AUTHENTICATED);
	}
	
	/**
	 * Tells the client about an object.
	 * 
	 * @throws IOException 
	 */
	public void writeObject(int id) throws IOException
	{
		if (!Container.containers.containsKey(id))
		{
			Server.logger.info("Requested object " + id + " not found.");
			this.codedoutput.writeRawVarint32(Protocol.MESSAGE);
			this.codedoutput.writeString("Requested object " + id + " not found");
			return;
		}
		
		// Give the client the details on the requested Container
		this.codedoutput.writeTag(Protocol.OBJECT);
		List<Container> contents = Container.containers.get(id);
		contents.get(0).write(this.codedoutput);
		
		// Give the client parent->content information
		for (int i = 1; i < contents.size(); i++)
			this.codedoutput.writeInt32(contents.get(i).getID());
	}

	/**
	 * Flushes queued writes to the client.
	 * 
	 * @param channel
	 * 			A SocketChannel instance
	 */
	public void flush(SocketChannel channel)
	{
		// Write pending data
		int numWrite = this.codedoutput.position();
		if (numWrite > 0)
		{			
			byte[] dataCopy = new byte[numWrite];
            System.arraycopy(this.outputbuffer, 0, dataCopy, 0, numWrite);
            Server.nio.send(channel, dataCopy);
			this.codedoutput.clear();
		}
	}
}
